﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Windows;

namespace NavFx
{
	/// <summary>
	/// Component handling navigation from one silverlight page to another
	/// </summary>
	public class Navigator: IDisposable, IEnumerable<IPage>
	{
		#region Private members

		private int currentIndex = -1;
		private Dictionary<string, IPage> pages = null;
		private Stack<string> forwardHistory = null;
		private Stack<string> pageHistory = null;

		/// <summary>
		/// Helper method to remove  all references to a value from a stack
		/// </summary>
		/// <param name="stack">Stack to remove from</param>
		/// <param name="value">Value to be removed</param>
		private void removeFromStack(Stack<string> stack, string value)
		{
			Stack<string> temp = new Stack<string>();
			int itemCount = stack.Count;
			for(int i = 0; i < itemCount; i++)
			{
				if(stack.Peek() == value)
				{
					//found the value, get rid of it
					//and stop processing
					stack.Pop();
				}
				else
				{
					//stick this value in the temporary
					//cache ready for restoration
					temp.Push(stack.Pop());
				}
			}

			//restore the stack
			itemCount = temp.Count;
			for(int i = 0; i < itemCount; i++)
			{
				stack.Push(temp.Pop());
			}
		}

		/// <summary>
		/// Reference to the Transitor handling page transitions
		/// </summary>
		public ITransitor Transitor
		{
			get;
			set;
		}

		/// <summary>
		/// Collection of pages to be handled by the Navigator
		/// </summary>
		private Dictionary<string, IPage> Pages
		{
			get
			{
				if(this.pages == null)
				{
					this.pages = new Dictionary<string, IPage>();
				}
				return this.pages;
			}

			set
			{
				this.pages = value;
			}
		}

		/// <summary>
		/// Cache of page paths that the user has come back from
		/// </summary>
		private Stack<string> ForwardHistory
		{
			get
			{
				if(this.forwardHistory == null)
				{
					this.forwardHistory = new Stack<string>();
				}
				return this.forwardHistory;
			}

			set
			{
				this.forwardHistory = value;
			}
		}

		/// <summary>
		/// Cache of page paths the user has previously been on
		/// </summary>
		private Stack<string> PageHistory
		{
			get
			{
				if(this.pageHistory == null)
				{
					this.pageHistory = new Stack<string>();
				}

				return this.pageHistory;
			}

			set
			{
				this.pageHistory = value;
			}
		}


		#endregion

		#region Public members
		
		/// <summary>
		/// Raised when a request to load external pages has completed
		/// </summary>
		public event EventHandler<PageLoadCompletedEventArgs> PageLoadCompleted;

		/// <summary>
		/// Count of pages currently registered with Navigator
		/// </summary>
		public int PageCount
		{
			get
			{
				return this.Pages.Count;
			}
		}

		/// <summary>
		/// Path of the current page
		/// </summary>
		public string CurrentPage
		{
			get
			{
				//Current page is always the top
				//of the stack used for caching
				//page history
				if(this.PageHistory.Count > 0)
				{
					return this.PageHistory.Peek();
				}
				else
				{
					return String.Empty;
				}
			}
		}

		/// <summary>
		/// Index of the current page
		/// </summary>
		public int CurrentIndex
		{
			get
			{
				return this.currentIndex;
			}

			set
			{
				this.currentIndex = value;
			}
		}


		/// <summary>
		/// Indicates if the CurrentIndex is consistent with the CurrentPage
		/// </summary>
		public bool IsIndexConsistent
		{
			get
			{
				if(this.Pages.Count > 0)
				{
					//user Linq extention method to find the page at the current index
					return this.Pages.ElementAt(this.CurrentIndex).Value.Path == this.CurrentPage;
				}

				return false;
			}

		}

		/// <summary>
		/// Register a page the Navigator will handle
		/// </summary>
		/// <param name="page">Page to be handled</param>
		public void RegisterPage(IPage page)
		{
			if(!this.IsPageRegistered(page.Path))
			{
				if(page is IHostPage && !(page is HostPage))
				{
					//the page is a hosting page that is not
					//based on the default implementation
					//try giving it a reference to
					//this navigator
					try
					{
						((IHostPage)page).Navigator = this;
					}
					catch(NotImplementedException)
					{
						//ignore any error here, a setter may not be
						//implemented, the page is probably relying
						//on the applications navigator as the
						//HostBase implementation provided by NavFx does
					}
					catch
					{
						//all other errors need to be passed on
						throw;
					}
				}
				this.Pages.Add(page.Path, page);
			}
		}

		/// <summary>
		/// Checks whether a page is registered with a specified path
		/// </summary>
		/// <param name="path">Path of the target page</param>
		/// <returns>True if the page has been registered otherwise false</returns>
		public bool IsPageRegistered(string path)
		{
			if(this.Pages.Count > 0)
			{
				return this.Pages.ContainsKey(path);
			}

			return false;
		}

		/// <summary>
		/// Navigate to a specified page without updating the current index of the page
		/// </summary>
		/// <param name="path">Path of page to be displayed</param>
		public void GoToPage(string path)
		{
			this.GoToPage(path, false);
		}

		/// <summary>
		/// Navigate to a specified page without updating the current index, setting the Target property of the current Transitor
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetPath">Path of the page to be set as the Transitor Target</param>
		public void GoToPage(string path, string targetPath)
		{
			this.GoToPage(path, targetPath, false);
		}

		/// <summary>
		/// Navigate to a specified page setting another page in cache as the host target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetPath">Path of the page to be set as the Transitor target</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to a page already registered with Navigator
		/// </remarks>
		public void GoToPage(string path, string targetPath, bool updateIndex)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target path has been provided
			if(String.IsNullOrEmpty(targetPath))
			{
				throw (new ArgumentException("Missing argument", "targetPath"));
			}

			if(!this.IsPageRegistered(targetPath))
			{
				//the requested page has not been registered
				throw (new PageNotRegisteredException(String.Format("No page registered with path: {0}", targetPath)));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = this.GetPage(targetPath);
			this.GoToPage(path, updateIndex);

		}

		/// <summary>
		/// Navigate to a specified page after loading it from an external library if neccesary setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetPath">Path of the page to be set as the Transitor target</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <param name="libraryUri">Uri of the dll that contains the pages to be loaded</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to a page already registered with Navigator and load the target page from an external library
		/// if it is not already registered
		/// </remarks>
		public void GoToPage(string path, string targetPath, bool updateIndex, Uri libraryUri)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target page has been provided
			if(targetPath == null)
			{
				throw (new ArgumentException("Missing argument", "targetPage"));
			}

			//validate that the libraryUri has been provided
			if(libraryUri == null)
			{
				throw (new ArgumentException("Missing argument", "libraryUri"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = this.GetPage(targetPath);
			this.GoToPage(path, updateIndex, libraryUri);

		}

		/// <summary>
		/// Navigate to a specified page after loading it from an external library if neccesary setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetPath">Path of the page to be set as the Transitor target</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to a page already registered with Navigator and load the target page from an external library
		/// if it is not already registered
		/// </remarks>
		public void GoToPage(string path, string targetPath, bool updateIndex, string libraryUrl)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target page has been provided
			if(String.IsNullOrEmpty(targetPath))
			{
				throw (new ArgumentException("Missing argument", "targetPath"));
			}

			//validate that the libraryUrl has been provided
			if(String.IsNullOrEmpty(libraryUrl))
			{
				throw (new ArgumentException("Missing argument", "libraryUrl"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = this.GetPage(targetPath);
			this.GoToPage(path, updateIndex, libraryUrl);

		}

		/// <summary>
		/// Navigate to a specified page setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetPage">Reference to the IHostPage to be set as the Target</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to an IHostPage instance already prepared
		/// </remarks>
		public void GoToPage(string path, IHostPage targetPage, bool updateIndex)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target page has been provided
			if(targetPage == null)
			{
				throw (new ArgumentException("Missing argument", "targetPage"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = targetPage;
			this.GoToPage(path, updateIndex);

		}

		/// <summary>
		/// Navigate to a specified page after loading it from an external library if neccesary setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetPage">Reference to the IHostPage to be set as the Target</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <param name="libraryUri">Uri of the dll that contains the pages to be loaded</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to an IHostPage instance already prepared and load the target page from an external library
		/// if it is not already registered
		/// </remarks>
		public void GoToPage(string path, IHostPage targetPage, bool updateIndex, Uri libraryUri)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target page has been provided
			if(targetPage == null)
			{
				throw (new ArgumentException("Missing argument", "targetPage"));
			}

			//validate that the libraryUri has been provided
			if(libraryUri == null)
			{
				throw (new ArgumentException("Missing argument", "libraryUri"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = targetPage;
			this.GoToPage(path, updateIndex, libraryUri);

		}

		/// <summary>
		/// Navigate to a specified page after loading it from an external library if neccesary setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetPage">Reference to the IHostPage to be set as the Target</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to an IHostPage instance already prepared and load the target page from an external library
		/// if it is not already registered
		/// </remarks>
		public void GoToPage(string path, IHostPage targetPage, bool updateIndex, string libraryUrl)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target page has been provided
			if(targetPage == null)
			{
				throw (new ArgumentException("Missing argument", "targetPage"));
			}

			//validate that the libraryUrl has been provided
			if(String.IsNullOrEmpty(libraryUrl))
			{
				throw (new ArgumentException("Missing argument", "libraryUrl"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = targetPage;
			this.GoToPage(path, updateIndex, libraryUrl);

		}

		/// <summary>
		/// Navigate to a specified page setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetApp">Reference to the Application to be set as a the Target for the current Transitor</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to an Application instance already prepared
		/// </remarks>
		public void GoToPage(string path, System.Windows.Application targetApp, bool updateIndex)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target application has been provided
			if(targetApp == null)
			{
				throw (new ArgumentException("Missing argument", "targetApp"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = targetApp;
			this.GoToPage(path, updateIndex);

		}

		/// <summary>
		/// Navigate to a specified page after loading it from an external library if neccesary setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetApp">Reference to the Application to be set as a the Target for the current Transitor</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <param name="libraryUri">Uri of the dll that contains the pages to be loaded</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to an Application instance already prepared and load the target page from an external library
		/// if it is not already registered
		/// </remarks>
		public void GoToPage(string path, System.Windows.Application targetApp, bool updateIndex, Uri libraryUri)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target application has been provided
			if(targetApp == null)
			{
				throw (new ArgumentException("Missing argument", "targetApp"));
			}

			//validate that the libraryUri has been provided
			if(libraryUri == null)
			{
				throw (new ArgumentException("Missing argument", "libraryUri"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = targetApp;
			this.GoToPage(path, updateIndex, libraryUri);

		}

		/// <summary>
		/// Navigate to a specified page after loading it from an external library if neccesary setting a HostPage as a target
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="targetApp">Reference to the Application to be set as a the Target for the current Transitor</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <param name="libraryUrl">Url of the dll that contains the pages to be loaded</param>
		/// <remarks>
		/// Use this overload to have Navigator set the Target property of the current Transitor
		/// to an Application instance already prepared and load the target page from an external library
		/// if it is not already registered
		/// </remarks>
		public void GoToPage(string path, System.Windows.Application targetApp, bool updateIndex, string libraryUrl)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//target can be set
				throw (new TransitorNotSetException());
			}

			//validate target application has been provided
			if(targetApp == null)
			{
				throw (new ArgumentException("Missing argument", "targetApp"));
			}

			//validate that the libraryUrl has been provided
			if(String.IsNullOrEmpty(libraryUrl))
			{
				throw (new ArgumentException("Missing argument", "libraryUrl"));
			}

			//set the target and navigate to requested page
			this.Transitor.Target = targetApp;
			this.GoToPage(path, updateIndex, libraryUrl);

		}

		/// <summary>
		/// Navigate to a specified page explicitly stating whether the current index should be updated
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <remarks>
		/// Use this overload if you are need to support Next and Previous operations to
		/// navigate to other pages after the target is loaded, for example if you
		/// are creating a wizard that supports jumping to specific pages
		/// </remarks>
		public void GoToPage(string path, bool updateIndex)
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//any navigation can be performed
				throw (new TransitorNotSetException());
			}

			if(String.IsNullOrEmpty(path))
			{
				throw (new ArgumentException("Missing argument", "path"));
			}

			if(!this.Pages.ContainsKey(path))
			{
				//the requested page has not been registered
				throw (new PageNotRegisteredException(String.Format("No page registered with path: {0}", path)));
			}

			if(this.CurrentPage.Equals(path))
			{
				//we are already on the required page
				return;
			}

			//Capture any current page as the previous page
			IPage previousPage = null;
			if(this.PageHistory.Count > 0)
			{
				previousPage = this.Pages[this.CurrentPage];
			}

			//Capture the registered page as the next to show
			IPage nextPage = this.Pages[path];

			//Add the next page to history
			//making it the current page
			this.PageHistory.Push(path);

			//going to a specific page so
			//nothing to go forward to clear
			//the forward history cache
			this.ForwardHistory.Clear();

			if(updateIndex)
			{
				//update the current index pointer so that
				//Next and Previous navigation still works
				int index = 0;
				foreach(KeyValuePair<string, IPage> currentPage in this.Pages)
				{
					if(currentPage.Value.Path == path)
					{
						this.CurrentIndex = index;
					}
					index++;
				}
			}

			//carry out the page transition
			this.Transitor.TransitionPage(previousPage, nextPage);
		}

		/// <summary>
		/// Ensures the page is loaded and registered, then displays the page
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <param name="libraryUri">Uri of the dll that contains the pages to be loaded</param>
		/// <remarks>
		/// Set updateIndex to true if you are need to support Next and Previous operations to
		/// navigate to other pages after the target is loaded, for example if you
		/// are creating a wizard that supports jumping to specific pages
		/// 
		/// libraryUri must be a Relative url to the dll file, so far it would
		/// seem the dll must be in the same folder as the xap.
		/// </remarks>
		public void GoToPage(string path, bool updateIndex, Uri libraryUri)
		{
			if(!this.IsPageRegistered(path))
			{
				WebClient webClient = new WebClient();
				webClient.OpenReadCompleted += LibraryLoadCompleted;
				webClient.OpenReadAsync(libraryUri, path);
			}
			else
			{
				this.GoToPage(path, updateIndex);
			}
		}

		/// <summary>
		/// Ensures the page is loaded and registered, then displays the page
		/// </summary>
		/// <param name="path">Path of the page to be displayed</param>
		/// <param name="updateIndex">Indicates whether the current index tracker should be updated</param>
		/// <param name="libraryUrl">Url of the dll that contains the pages to be loaded</param>
		/// <remarks>
		/// Set updateIndex to true if you are need to support Next and Previous operations to
		/// navigate to other pages after the target is loaded, for example if you
		/// are creating a wizard that supports jumping to specific pages
		/// 
		/// libraryUrl must be a Relative url to the dll file, so far it would
		/// seem the dll must be in the same folder as the xap.
		/// </remarks>
		public void GoToPage(string path, bool updateIndex, string libraryUrl)
		{
			//prepare the uri and call the overload that uses it
			this.GoToPage(path, updateIndex, new Uri(libraryUrl, UriKind.Relative));
		}

		/// <summary>
		/// Callback handler for loading pages from a libary
		/// </summary>
		/// <param name="sender">The WebClient that handled the request</param>
		/// <param name="e">Event arguments indicating state and containing the restult</param>
		private void LibraryLoadCompleted(object sender, OpenReadCompletedEventArgs e)
		{
			if(e.Error == null && !e.Cancelled)
			{
				WebClient webClient = (WebClient)sender;
				string targetPage = e.UserState.ToString();
				this.LoadPages(e.Result, targetPage);

				//raise the completed event there are any subscribers
				if(this.PageLoadCompleted != null)
				{
					this.PageLoadCompleted(this, new PageLoadCompletedEventArgs(targetPage));
				}
			}
		}

		/// <summary>
		/// Navigate to the next registered page
		/// </summary>
		/// <remarks>
		/// Assumes pages have been registered in the sequence they need to be displayed
		/// </remarks>
		/// <returns>True if more pages following the current page, false otherwise</returns>
		public bool NextPage()
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//any navigation can be performed
				throw (new TransitorNotSetException());
			}

			if(this.Pages.Count == 0)
			{
				//no pages registereed
				throw (new PageNotRegisteredException("No pages have been registered"));
			}

			if(!this.IsIndexConsistent)
			{
				//the index tracker is not consistent with the current page
				//the developer is not controlling page navigation properley
				throw (new InconsistentIndexException());
			}


			this.CurrentIndex++;
			if(this.CurrentIndex >= this.Pages.Count)
			{
				//no more pages to move to
				return false;
			}

			//Capture any current page as the previous page
			IPage previousPage = null;
			if(!String.IsNullOrEmpty(this.CurrentPage))
			{
				previousPage = this.Pages[this.CurrentPage];
			}

			//Capture the next page in the sequence
			IPage nextPage = this.Pages.ElementAt(this.CurrentIndex).Value;

			//Add the next page in sequence to history
			//making it the current page
			this.PageHistory.Push(nextPage.Path);

			//going to a specific page so
			//nothing to go forward to clear
			//the forward history cache
			this.ForwardHistory.Clear();

			//carry out the page transition
			this.Transitor.TransitionPage(previousPage, nextPage);

			return this.CurrentIndex < (this.Pages.Count - 1);
		}

		/// <summary>
		/// Navigate to the previous registered page in sequence
		/// </summary>
		/// <remarks>
		/// Assumes pages have been registered in the sequence they need to be displayed
		/// </remarks>
		/// <returns>True if more pages precediing the current page, false otherwise</returns>
		public bool PreviousPage()
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//any navigation can be performed
				throw (new TransitorNotSetException());
			}

			if(this.Pages.Count == 0)
			{
				//no pages registereed
				throw (new PageNotRegisteredException("No pages have been registered"));
			}

			if(!this.IsIndexConsistent)
			{
				//the index tracker is not consistent with the current page
				//the developer is not controlling page navigation properley
				throw (new InconsistentIndexException());
			}


			this.CurrentIndex--;
			if(this.CurrentIndex < 0)
			{
				//no more pages to move to
				return false;
			}

			//Capture any current page as the previous page
			IPage previousPage = null;
			if(!String.IsNullOrEmpty(this.CurrentPage))
			{
				previousPage = this.Pages[this.CurrentPage];
			}

			//Capture the next page in the sequence
			IPage nextPage = this.Pages.ElementAt(this.CurrentIndex).Value;

			//Add the previous page in sequence to history
			//making it the current page
			this.PageHistory.Push(nextPage.Path);

			//going to a specific page so
			//nothing to go forward to clear
			//the forward history cache
			this.ForwardHistory.Clear();

			//carry out the page transition
			this.Transitor.TransitionPage(previousPage, nextPage);

			return this.CurrentIndex > 0;
		}


		/// <summary>
		/// Navigate to the first page in forward history
		/// </summary>
		/// <returns>True if there are more pages in forward history, false otherwise</returns>
		public bool GoForward()
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//any navigation can be performed
				throw (new TransitorNotSetException());
			}

			if(this.ForwardHistory.Count == 0)
			{
				//no pages in forward history
				return false;
			}

			//Capture any current page as the previous page
			IPage previousPage = null;
			if(!String.IsNullOrEmpty(this.CurrentPage))
			{
				previousPage = this.Pages[this.CurrentPage];
			}

			//Capture the page to move to
			IPage nextPage = this.Pages[this.ForwardHistory.Pop()];

			//Add the previous page in sequence to history
			//making it the current page
			this.PageHistory.Push(nextPage.Path);

			//carry out the page transition
			this.Transitor.TransitionPage(previousPage, nextPage);

			return this.ForwardHistory.Count > 0;
		}


		/// <summary>
		/// Navigate to the first page in history
		/// </summary>
		/// <returns>True if there are more pages in history, false otherwise</returns>
		public bool GoBack()
		{
			if(this.Transitor == null)
			{
				//Transitor property must be set before
				//any navigation can be performed
				throw (new TransitorNotSetException());
			}

			if(this.PageHistory.Count == 0)
			{
				//no pages in history
				return false;
			}

			//Capture any current page as the previous page
			IPage previousPage = null;
			if(!String.IsNullOrEmpty(this.CurrentPage))
			{
				previousPage = this.Pages[this.PageHistory.Pop()];
			}

			//Capture the page to move to
			IPage nextPage = this.Pages[this.CurrentPage];

			//Add the previous page in sequence to history
			//making it the current page
			this.ForwardHistory.Push(previousPage.Path);

			//carry out the page transition
			this.Transitor.TransitionPage(previousPage, nextPage);

			return this.PageHistory.Count > 0;
		}

		/// <summary>
		/// Automatically load and register pages (user conrols) in an assembly loaded as a stream
		/// </summary>
		/// <param name="assemblyStream">Target assembly read into a stream</param>
		public void LoadPages(Stream assemblyStream)
		{
			this.LoadPages(assemblyStream, null);
		}

		/// <summary>
		/// Automatically load and register pages (user conrols) in an assembly loaded as a stream, displaying a specified page on completion
		/// </summary>
		/// <param name="assemblyStream">Target assembly read into a stream</param>
		/// <param name="initialPagePath">Page to display after loading</param>
		public void LoadPages(Stream assemblyStream, string initialPagePath)
		{
			using(assemblyStream)
			{
				AssemblyPart assemblyPart = new AssemblyPart();
				Assembly targetAssembly = null;
				try
				{
					targetAssembly = assemblyPart.Load(assemblyStream);
				}
				catch(Exception ex)
				{
					throw (new AssemblyLoadException("Failed to load assembly from stream. LoadPages could not register any pages.", ex));
				}

				//got an assembly loaded
				//register all valid pages in the assembly
				Type[] assemblyTypes = targetAssembly.GetTypes();
				foreach(var type in assemblyTypes)
				{
					Type[] typeInterfaces = type.GetInterfaces();
					foreach(var item in typeInterfaces)
					{
						if(item.Equals(typeof(IPage)))
						{
							IPage page = (IPage)targetAssembly.CreateInstance(type.FullName);
							this.RegisterPage(page);
							break;
						}
					}
				}

				//display any page specified as the initial page
				if(!String.IsNullOrEmpty(initialPagePath))
				{
					this.GoToPage(initialPagePath);
				}
			}
		}

		/// <summary>
		/// Removes cached pages registered with Navigator
		/// </summary>
		/// <param name="clearPinned">Indicates if Pinned pages should also be cleared</param>
		public void ClearCache(bool clearPinned)
		{
			string[] pageKeys = this.Pages.Keys.ToArray<string>();
			IPage page;
			foreach(string key in pageKeys)
			{
				page = this.Pages[key];
				if(!page.IsPinned || clearPinned)
				{
					this.RemovePage(page.Path);
				}
			}
		}

		/// <summary>
		/// Remove a page from the Navigators cache and clean up any history
		/// </summary>
		/// <param name="path">Path value of the page to be removed</param>
		public void RemovePage(string path)
		{
			//remove from history stacks
			this.removeFromStack(this.ForwardHistory, path);
			this.removeFromStack(this.PageHistory, path);

			//finally remove the page from the actual cache
			this.Pages.Remove(path);
		}

		/// <summary>
		/// Identifies if a specified page is pinned
		/// </summary>
		/// <param name="path">Path of the target page</param>
		/// <returns>True if the page is pinned false otherwise</returns>
		public bool IsPinned(string path)
		{
			return this.GetPage(path).IsPinned;
		}

		/// <summary>
		/// Retrieve a page from the cache
		/// </summary>
		/// <typeparam name="TPage">Type of the requested page</typeparam>
		/// <param name="path">Path identifying the page</param>
		/// <returns>The requested page or null if not present</returns>
		public TPage GetPage<TPage>(string path)
		{
			return (TPage)this.Pages[path];
		}

		/// <summary>
		/// Retrieve a page from the cache
		/// </summary>
		/// <param name="path">Path identifying the page</param>
		/// <returns>The requested page or null if not present</returns>
		public IPage GetPage(string path)
		{
			return this.GetPage<IPage>(path);
		}

		/// <summary>
		/// Indexer supporting use of Navigator as an array of IPages
		/// </summary>
		/// <param name="index">Index of required item</param>
		/// <returns>IPage at index or null if none found</returns>
		public IPage this[int index]
		{
			get
			{
				return this.Pages.ElementAt<KeyValuePair<string, IPage>>(index).Value;
			}
		}

		/// <summary>
		/// Indexer supporting use of Navigator as an array of IPages
		/// </summary>
		/// <param name="path">Path value for the required IPage</param>
		/// <returns>IPage with specified Path or null if none found</returns>
		public IPage this[string path]
		{
			get
			{
				return this.Pages[path];
			}
		}

		/// <summary>
		/// Returns strongly typed reference to the Transitor
		/// </summary>
		/// <typeparam name="TTransitor">Type of Transitor</typeparam>
		/// <returns>Reference to Transitor or null via default(TTransitor)</returns>
		public TTransitor GetTransitor<TTransitor>()
		{
			if(this.Transitor != null)
			{
				return (TTransitor)this.Transitor;
			}

			return default(TTransitor);
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Default constructor
		/// </summary>
		public Navigator()
		{
			//nothing to do
		}


		/// <summary>
		/// Constructor overload supporting provision of the ITransitor to use
		/// </summary>
		/// <param name="transitor">Instance of transition handler</param>
		public Navigator(ITransitor transitor)
			: this()
		{
			this.Transitor = transitor;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Clean up resources being used by the Navigator
		/// </summary>
		public void Dispose()
		{
			this.PageHistory.Clear();
			this.ForwardHistory.Clear();
			this.Pages.Clear();

			this.PageHistory = null;
			this.ForwardHistory = null;
			this.Pages = null;
		}

		#endregion

		#region IEnumerable<IPage> Members

		/// <summary>
		/// Supports foreach iteration of the page collection directly by Navigator
		/// </summary>
		/// <returns>Enumerator providing access to the pages registered with Navigator</returns>
		public IEnumerator<IPage> GetEnumerator()
		{
			foreach(KeyValuePair<string, IPage> item in this.Pages)
			{
				yield return item.Value;
			}
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// Supports foreach iteration of the page collection directly by Navigator
		/// </summary>
		/// <returns>Enumerator providing access to the pages registered with Navigator</returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
	}
}
